//
// Copyright 2021 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use './string-ext';

/// Validates a theme's tokens and throws an error if incorrect tokens are
/// present or any tokens are missing.
///
/// Use this in internal `theme-styles()` mixins to validate library-provided
/// `$theme` maps and ensure that all tokens are correct and present.
///
/// @example - scss
///   @mixin theme-styles($theme) {
///     $theme: theme.validate-theme-styles($light-theme, $theme);
///     $theme: theme.create-theme-vars($theme, checkbox);
///   }
///
/// @throw If any tokens are invalid or missing.
/// @param {Map|List} $reference-theme - A reference theme Map whose token keys
///     will be used to validate the user-provided theme (or list of tokens).
/// @param {Map} $theme - The theme Map to validate.
/// @return {Map} The validated theme Map.
@function theme-styles($reference-theme, $theme, $require-all: true) {
  $valid-tokens: $reference-theme;
  @if meta.type-of($reference-theme) == 'map' {
    $valid-tokens: map.keys($reference-theme);
  }

  $theme: _validate-theme-tokens(
    $valid-tokens,
    $theme,
    $require-all: $require-all
  );

  @return $theme;
}

/// Validates a theme's tokens and values and throws an error if incorrect
/// tokens are present or invalid values are provided.
///
/// Use this in `theme()` mixins to validate user-provided `$theme` maps before
/// providing the value to `theme.create-theme-vars()`.
///
/// @example - scss
///   @mixin theme($theme) {
///     $theme: validate.theme($light-theme, $theme);
///     $theme: theme.create-theme-vars($theme, checkbox);
///   }
///
/// @throw If any tokens or values are invalid.
/// @param {Map|List} $reference-theme - A reference theme Map whose token keys
///     will be used to validate the user-provided theme (or list of tokens).
/// @param {Map} $theme - User-provided theme Map to validate.
/// @return {Map} The validated user-provided theme Map.
@function theme($reference-theme, $theme) {
  $valid-tokens: $reference-theme;
  @if meta.type-of($reference-theme) == 'map' {
    $valid-tokens: map.keys($reference-theme);
  }

  $theme: _validate-theme-tokens($valid-tokens, $theme, $require-all: false);

  @return $theme;
}

/// Validates input for a `theme` mixin and throws an error if incorrect tokens
/// are present or optionally missing if `$require-all` is true.
///
/// @throw If any tokens are invalid or optionally missing.
/// @param {List} $valid-tokens - A List of token keys to validate the theme.
/// @param {Map} $theme - The theme Map to validate.
/// @param {Bool} $require-all [false] - If true, throw an error if the theme
///     is missing tokens from the list.
/// @return {Map} The validated theme Map.
@function _validate-theme-tokens($valid-tokens, $theme, $require-all: false) {
  $missing-tokens: ();
  $unsupported-tokens: ();

  @each $token, $value in $theme {
    @if $token != null and list.index($valid-tokens, $token) == null {
      $unsupported-tokens: list.append(
        $unsupported-tokens,
        $token,
        $separator: comma
      );
    }
  }

  @if $require-all {
    // TODO(b/203778922): Remove when type composite tokens are removed
    $ignore-suffix: (
      // Ignore composite font tokens
      '-type'
    );

    @each $token in $valid-tokens {
      $missing: map.get($theme, $token) == null;
      @if $missing {
        @each $suffix in $ignore-suffix {
          @if string-ext.has-suffix($token, $suffix) {
            $missing: false;
          }
        }
      }

      @if $missing {
        $missing-tokens: list.append(
          $missing-tokens,
          $token,
          $separator: comma
        );
      }
    }
  }

  @if list.length($unsupported-tokens) > 0 {
    @error 'The following tokens are invalid: #{$unsupported-tokens}.';
  }

  @if list.length($missing-tokens) > 0 {
    @error 'The following required tokens are missing: #{$missing-tokens}.';
  }

  @return $theme;
}
